# plugs/karma.py
#
#

""" karma plugin """

__copyright__ = 'this file is in the public domain'

from gozerbot.commands import cmnds
from gozerbot.examples import examples
from gozerbot.redispatcher import rebefore
from gozerbot.datadir import datadir
from gozerbot.generic import handle_exception, rlog, lockdec
from gozerbot.statdict import Statdict
from gozerbot.users import users
from gozerbot.aliases import aliases
from gozerbot.plughelp import plughelp
from plugs.links import links
import thread, pickle, time

plughelp.add('karma', 'maintain karma of items .. use ++ to raise karma by 1 \
or use -- to lower by 1 .. reason might be given after a "#"')

savelist = []

class Karma:

    """ holds karma data """

    def __init__(self, ddir):
        rlog(0, 'karma', 'reading %s' % datadir + '/karma')
        self.datadir = ddir
        self.lock = thread.allocate_lock()
        try:
            karmafile = open(ddir + '/karma', 'r')
            self.karma = pickle.load(karmafile)
            karmafile.close()
        except:
            self.karma = {}
        try:
            reasonupfile = open(ddir + '/reasonup', 'r')
            self.reasonup = pickle.load(reasonupfile)
            reasonupfile.close()
        except:
            self.reasonup = {}
        try:
            reasondownfile = open(ddir + '/reasondown', 'r')
            self.reasondown = pickle.load(reasondownfile)
            reasondownfile.close()
        except:
            self.reasondown = {}
        try:
            whoupfile = open(ddir + '/whoup', 'r')
            self.whoup = pickle.load(whoupfile)
            whoupfile.close()
        except:
            self.whoup = {}
        try:
            whodownfile = open(ddir + '/whodown', 'r')
            self.whodown = pickle.load(whodownfile)
            whodownfile.close()
        except:
            self.whodown = {}

    def size(self):
        return len(self.karma)
        
    def save(self):
        """ save karma data """
        try:
            self.lock.acquire()
            karmafile = open(self.datadir + '/karma', 'w')
            pickle.dump(self.karma, karmafile)
            karmafile.close()
            rlog(1, 'karma', '%s/karma saved' % self.datadir)
            reasonupfile = open(self.datadir + '/reasonup', 'w')
            pickle.dump(self.reasonup, reasonupfile)
            reasonupfile.close()
            rlog(1, 'karma', '%s/reasonup saved' % self.datadir)
            reasondownfile = open(self.datadir + '/reasondown', 'w')
            pickle.dump(self.reasondown, reasondownfile)
            reasondownfile.close()
            rlog(1, 'karma', '%s/reasondown saved' % self.datadir)
            whoupfile = open(self.datadir + '/whoup', 'w')
            pickle.dump(self.whoup, whoupfile)
            whoupfile.close()
            rlog(1, 'karma', '%s/whoup saved' % self.datadir)
            whodownfile = open(self.datadir + '/whodown', 'w')
            pickle.dump(self.whoup, whodownfile)
            whodownfile.close()
            rlog(1, 'karma', '%s/whodown saved' % self.datadir)
        finally:
            self.lock.release()

    def add(self, item, value):
        """ set karma value of item """
        self.karma[item.lower()] = value

    def delete(self, item):
        """ delete karma item """
        item = item.lower()
        try:
            del self.karma[item]
            return 1
        except KeyError:
            return 0

    def get(self, item):
        """ get karma of item """
        item = item.lower()
        if self.karma.has_key(item):
            return self.karma[item]
        else:
            return None

    def addwhy(self, item, updown, reason):
        """ add why of karma up/down """
        item = item.lower()
        if not self.karma.has_key(item):
            return 0
        reason = reason.strip()
        if updown == 'up':
            if self.reasonup.has_key(item):
                self.reasonup[item].append(reason)
            else:
                self.reasonup[item] = [reason]
        elif updown == 'down':
            if self.reasondown.has_key(item):
                self.reasondown[item].append(reason)
            else:
                self.reasondown[item] = [reason]
            
    def upitem(self, item, reason=None):
        """ up a karma item with/without reason """
        item = item.lower()
        if self.karma.has_key(item):
            self.karma[item] += 1
        else:
            self.karma[item] = 1
        if reason:
            reason = reason.strip()
            if self.reasonup.has_key(item):
                self.reasonup[item].append(reason)
            else:
                self.reasonup[item] = [reason]

    def down(self, item, reason=None):
        """ lower a karma item with/without reason """
        item = item.lower()
        if self.karma.has_key(item):
            self.karma[item] -= 1
        else:
            self.karma[item] = -1
        if reason:
            reason = reason.strip()
            if self.reasondown.has_key(item):
                self.reasondown[item].append(reason)
            else:
                self.reasondown[item] = [reason]

    def whykarmaup(self, item):
        """ get why of karma ups """
        item = item.lower()
        if self.reasonup.has_key(item):
            return self.reasonup[item]

    def whykarmadown(self, item):
        """ get why of karma downs """
        item = item.lower()
        if self.reasondown.has_key(item):
            return self.reasondown[item]

    def setwhoup(self, item, nick):
        """ set who upped a karma item """
        item = item.lower()
        if self.whoup.has_key(item):
            self.whoup[item].append(nick)
        else:
            self.whoup[item] = [nick]

    def setwhodown(self, item, nick):
        """ set who lowered a karma item """
        item = item.lower()
        if self.whodown.has_key(item):
            self.whodown[item].append(nick)
        else:
            self.whodown[item] = [nick]

    def getwhoup(self, item):
        """ get list of who upped a karma item """
        item = item.lower()
        try:
            return self.whoup[item]
        except KeyError:
            return None

    def getwhodown(self, item):
        """ get list of who downed a karma item """
        item = item.lower()
        try:
            return self.whodown[item]
        except KeyError:
            return None

    def good(self, limit=10):
        """ show top 10 of karma items """
        statdict = Statdict()
        for i in self.karma.keys():
            statdict.upitem(i, value=self.karma[i])
        return statdict.top(limit=limit)

    def bad(self, limit=10):
        """ show lowest 10 of negative karma items """
        statdict = Statdict()
        for i in self.karma.keys():
            statdict.upitem(i, value=self.karma[i])
        return statdict.down(limit=limit)

karma = Karma(datadir)
savelist.append(karma)

ratelimited = []
limiterlock = thread.allocate_lock()
limlock = lockdec(limiterlock)

@limlock
def ratelimit(bot, ievent):
    """ karma rate limiter """
    waittime = 30
    limit = 2
    try:
        name = ievent.userhost
        # Create a state for this user and his/her karma-state if necessary
        if not bot.state.has_key(name):
            bot.state[name] = {}
        if not bot.state[name].has_key('karma'):
            bot.state[name]['karma'] = {'count': 0, 'time': time.time() } 
        # If the waittime has elapsed, reset the counter
        if time.time() > bot.state[name]['karma']['time'] + waittime:
            bot.state[name]['karma']['count'] = 0 
        # Update counter
        bot.state[name]['karma']['count'] += 1
        # If counter is too high, limit :)
        if bot.state[name]['karma']['count'] > limit:
            if name in ratelimited:
                return 0
            ievent.reply("karma limit reached, you'll have to wait %s \
seconds" % int(bot.state[name]['karma']['time'] + waittime - time.time()))
            ratelimited.append(name)
            return 0
        # Update time
        bot.state[name]['karma']['time'] = time.time()
        # Ratelimiting passed :)
        try:
            ratelimited.remove(name)
        except:
            pass
        return 1
    except Exception, ex:
        handle_exception(ievent)

def handle_karmaget(bot, ievent):
    """ karma-get <item> .. show karma of item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    else:
        item = ievent.rest
    result = karma.get(item)
    linklist = links[item]
    linkresult = ""
    total = 0
    if linklist:
        for i in linklist:
            linkkarma = karma.get(i)
            if linkkarma:
                total += linkkarma
                linkresult += "%s (%s) " % (i, linkkarma)
    if result:
        if linkresult:
            total += result
            ievent.reply("%s has karma %s .. %s .. total is %s" % (item, \
str(result), linkresult, total))
        else:
            ievent.reply("%s has karma %s" % (item, str(result)))
    else:
        ievent.reply("%s has no karma yet" % item)

cmnds.add('karma-get', handle_karmaget, ['USER', 'WEB'])
examples.add('karma-get', 'karma-get <item> .. show karma of <item>', \
'karma-get dunker')
aliases.data['karma'] = 'karma-get'

def handle_karmadel(bot, ievent):
    """ karma-del <item> .. delete karma item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.delete(item)
    if result:
        ievent.reply("%s deleted" % item)
    else:
        ievent.reply("can't delete %s" % item)

cmnds.add('karma-del', handle_karmadel, 'OPER')
examples.add('karma-del', 'karma-del <item> .. delete karma item', \
'karma-del dunker')

def handle_karmaup(bot, ievent, groups):
    """ <item>++ ['#' <reason>] .. increase karma of item with optional \
        reason """
    if not ratelimit(bot, ievent):
        return
    (item, reason) = groups
    item = item.strip().lower()
    karma.upitem(item, reason=reason)
    karma.setwhoup(item, ievent.nick)
    ievent.reply(item + ' has now karma ' + str(karma.get(item)))

rebefore.add(10, '^(.+)\+\+\s+#(.*)$', handle_karmaup, 'USER')
examples.add('++', "<item>++ ['#' <reason>] .. higher karma of item with 1 \
(use optional reason)", '1) gozerbot++ 2) gozerbot++ # top bot')

def handle_karmaup2(bot, ievent, groups):
    """ increase karma without reason """
    grouplist = list(groups) + [None, ]
    handle_karmaup(bot, ievent, grouplist)

rebefore.add(11, '^(.+)\+\+$', handle_karmaup2, 'USER')

def handle_karmadown(bot, ievent, groups):
    """ <item>-- ['#' <reason> .. decrease karma item with reason """
    if not ratelimit(bot, ievent):
        return
    (item, reason) = groups
    item = item.strip().lower()
    karma.down(item, reason=reason)
    karma.setwhodown(item, ievent.nick)
    ievent.reply(item + ' has now karma ' + str(karma.get(item)))

rebefore.add(10, '^(.+)\-\-\s+#(.*)$', handle_karmadown, 'USER')
examples.add('--', "<item>-- ['#' <reason>] .. lower karma of item with 1 \
(use optional reason)", '1) gozerbot-- 2) gozerbot-- # bad bot')

def handle_karmadown2(bot, ievent, groups):
    """ decrease karma item without reason """
    grouplist = list(groups) + [None, ]
    handle_karmadown(bot, ievent, grouplist)

rebefore.add(10, '^(.+)\-\-$', handle_karmadown2, 'USER')

def handle_karmawhyup(bot, ievent):
    """ karma-whyup <item> .. show why karma of item has been increased """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.whykarmaup(item)
    if result:
        ievent.reply('whykarmaup of %s: %s' % (item, ' .. '.join(result)))
    else:
        ievent.reply('%s has no reason for karmaup yet' % item)

cmnds.add('karma-whyup', handle_karmawhyup, ['USER', 'WEB'])
examples.add('karma-whyup', 'karma-whyup <item> .. show the reason why \
karma of <item> was raised', 'karma-whyup gozerbot')
aliases.data['wku'] = 'karma-whyup'

def handle_whykarmadown(bot, ievent):
    """ karma-whydown <item> .. show why karma of item has been decreased """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.whykarmadown(item)
    if result:
        ievent.reply('whykarmadown of %s: %s' % (item, ' .. '.join(result)))
    else:
        ievent.reply('%s has no reason for karmadown yet' % item)

cmnds.add('karma-whydown', handle_whykarmadown, ['USER', 'WEB'])
examples.add('karma-whydown', 'karma-whydown <item> .. show the reason why \
karma of <item> was lowered', 'karma-whydown gozerbot')
aliases.data['wkd'] = 'karma-whydown'

def handle_karmagood(bot, ievent):
    """ karma-good .. show top 10 karma items """
    result = karma.good(limit=10)
    if result:
        resultstr = ""
        for i in result:
            if i[1] > 0:
                resultstr += "%s: %s " % (i[0], i[1])
        ievent.reply('goodness = %s' % resultstr)
    else:
        ievent.reply('karma void')

cmnds.add('karma-good', handle_karmagood, ['USER', 'WEB'])
examples.add('karma-good', 'show top 10 karma', 'karma-good')
aliases.data['good'] = 'karma-good'

def handle_karmabad(bot, ievent):
    """ karma-bad .. show 10 most negative karma items """
    result = karma.bad(limit=10)
    if result:
        resultstr = ""
        for i in result:
            if i[1] < 0:
                resultstr += "%s: %s " % (i[0], i[1])
        ievent.reply('badness = %s' % resultstr)
    else:
        ievent.reply('karma void')

cmnds.add('karma-bad', handle_karmabad, ['USER', 'WEB'])
examples.add('karma-bad', 'show lowest top 10 karma', 'karma-bad')
aliases.data['bad'] = 'karma-bad'

def handle_whokarmaup(bot, ievent):
    """ karma-whoup <item> .. show who increased a karma item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.getwhoup(item)
    statdict = Statdict()
    if result:
        for i in result:
            statdict.upitem(i)
        resultstr = ""
        for i in statdict.top():
            resultstr += "%s = %s " % i
        ievent.reply(resultstr)
    else:
        ievent.reply('no whokarmaup data available for %s' % item)

cmnds.add('karma-whoup', handle_whokarmaup, ['USER', 'WEB'])
examples.add('karma-whoup', 'karma-whoup <item> .. show who raised the \
karma of <item>', 'karma-whoup gozerbot')

def handle_whokarmadown(bot, ievent):
    """ karma-whodown <item> .. show who decreased a karma item """
    if not ievent.rest:
        ievent.missing('<item>')
        return
    item = ievent.rest
    result = karma.getwhodown(item)
    statdict = Statdict()
    if result:
        for i in result:
            statdict.upitem(i)
        resultstr = ""
        for i in statdict.top():
            resultstr += "%s = %s " % i
        ievent.reply(resultstr)
    else:
        ievent.reply('no whokarmadown data available for %s' % item)

cmnds.add('karma-whodown', handle_whokarmadown, ['USER', 'WEB'])
examples.add('karma-whodown', 'karma-whodown <item> .. show who lowered \
the karma of <item>', 'karma-whodown gozerbot')
